package com.example.myapplication.markwon

import android.content.Context
import android.graphics.drawable.Drawable
import android.text.Spanned
import android.widget.TextView
import coil.Coil
import coil.ImageLoader
import coil.request.Disposable
import coil.request.ImageRequest
import coil.target.Target
import io.noties.markwon.AbstractMarkwonPlugin
import io.noties.markwon.MarkwonConfiguration
import io.noties.markwon.MarkwonSpansFactory
import io.noties.markwon.image.AsyncDrawable
import io.noties.markwon.image.AsyncDrawableLoader
import io.noties.markwon.image.AsyncDrawableScheduler
import io.noties.markwon.image.DrawableUtils
import io.noties.markwon.image.ImageSpanFactory
import org.commonmark.node.Image
import java.util.concurrent.atomic.AtomicBoolean

/**
 * Create by Tanzhenxing on 2025/8/7.
 * Description:
 */
class CoilImagesPlugin(
    coilStore: CoilStore,
    imageLoader: ImageLoader,
) : AbstractMarkwonPlugin() {
    private val coilAsyncDrawableLoader = CoilAsyncDrawableLoader(coilStore, imageLoader)

    override fun configureSpansFactory(builder: MarkwonSpansFactory.Builder) {
        builder.setFactory(Image::class.java, ImageSpanFactory())
    }

    override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
        builder.asyncDrawableLoader(coilAsyncDrawableLoader)
    }

    override fun beforeSetText(textView: TextView, markdown: Spanned) {
        AsyncDrawableScheduler.unschedule(textView)
    }

    override fun afterSetText(textView: TextView) {
        AsyncDrawableScheduler.schedule(textView)
    }

    interface CoilStore {
        fun load(drawable: AsyncDrawable): ImageRequest
        fun cancel(disposable: Disposable)
    }

    companion object {
        fun create(context: Context) : CoilImagesPlugin {
            return CoilImagesPlugin(object : CoilStore {
                override fun load(drawable: AsyncDrawable): ImageRequest {
                    return ImageRequest.Builder(context)
                        .data(drawable.destination)
                        .build()
                }

                override fun cancel(disposable: Disposable) {
                    disposable.dispose()
                }

            }, Coil.imageLoader(context))
        }

        fun create(context: Context, imageLoader: ImageLoader) : CoilImagesPlugin {
            return CoilImagesPlugin(object : CoilStore {
                override fun load(drawable: AsyncDrawable): ImageRequest {
                    return ImageRequest.Builder(context)
                        .data(drawable.destination)
                        .build()
                }

                override fun cancel(disposable: Disposable) {
                    disposable.dispose()
                }

            }, imageLoader)
        }

        fun create(coilStore: CoilStore, imageLoader: ImageLoader) : CoilImagesPlugin {
            return CoilImagesPlugin(coilStore, imageLoader)
        }
    }

    private class CoilAsyncDrawableLoader(
        private val coilStore: CoilStore,
        private val imageLoader: ImageLoader,
    ) : AsyncDrawableLoader() {
        private val cache = mutableMapOf<AsyncDrawable, Disposable>()
        override fun load(drawable: AsyncDrawable) {
            val loaded = AtomicBoolean(false)
            val target = AsyncDrawableTarget(drawable, loaded)
            val request = coilStore.load(drawable)
                .newBuilder()
                .target(target)
                .build()
            val disposable = imageLoader.enqueue(request)
            if (!loaded.get()) {
                loaded.set(true)
                cache[drawable] = disposable
            }
        }

        override fun cancel(drawable: AsyncDrawable) {
            cache.remove(drawable)?.let {
                coilStore.cancel(it)
            }
        }

        override fun placeholder(drawable: AsyncDrawable): Drawable? {
            return null
        }

        private inner class AsyncDrawableTarget(
            private val drawable: AsyncDrawable,
            private val loaded: AtomicBoolean,
        ) : Target {
            override fun onSuccess(result: Drawable) {
                if (cache.remove((drawable)) != null || !loaded.get()) {
                    loaded.set(true)
                    if (drawable.isAttached) {
                        DrawableUtils.applyIntrinsicBounds(result)
                        drawable.result = result
                    }
                }
            }

            override fun onStart(placeholder: Drawable?) {
                placeholder ?: return
                if (drawable.isAttached) {
                    DrawableUtils.applyIntrinsicBounds(placeholder)
                    drawable.result = placeholder
                }
            }

            override fun onError(error: Drawable?) {
                if (cache.remove(drawable) != null) {
                    if (error != null && drawable.isAttached) {
                        DrawableUtils.applyIntrinsicBounds(error)
                        drawable.result = error
                    }
                }
            }
        }
    }
}